﻿using DJLang;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace BlockOS.Client.Res
{
    public static class BlockModelLoadLib
    {
        public static readonly string CONVERTER_NAME = "uv-converters";
        public static readonly string BINDPOINTS_NAME = "bind-points";

        [DJFunc("byte")]
        public static IDJValue GetByte(DJEnv env, DJSeq args)
        {
            var str = args.PopManaged<string>();
            byte ans = 0;
            for (int i = 0; i < 8 && i < str.Length; i++)
            {
                ans <<= 1;
                if (str[i] == '1')
                {
                    ans |= 1;
                }
            }
            return new DJBasic<byte>(ans);
        }

        [DJFunc("vec2")]
        public static IDJValue MakeVector2(DJEnv env, DJSeq args)
        {
            return new DJBasic<Vector2>(new Vector2(args.Pop().ToFloat().val, args.Pop().ToFloat().val));
        }

        [DJFunc("vec3")]
        public static IDJValue MakeVector3(DJEnv env, DJSeq args)
        {
            return new DJBasic<Vector3>(new Vector3(args.Pop().ToFloat().val, args.Pop().ToFloat().val, args.Pop().ToFloat().val));
        }

        [DJFunc("uv")]
        public static IDJValue MakeUV(DJEnv env, DJSeq args)
        {
            return new DJBasic<Vector4>(new Vector4(args.Pop().ToFloat().val, args.Pop().ToFloat().val, args.Pop().ToFloat().val, args.Pop().ToFloat().val));
        }

        [DJFunc("vertex")]
        public static IDJValue MakeVertex(DJEnv env, DJSeq args)
        {
            return new DJBasic<VertexData>(new VertexData(args.PopValue<Vector3>(), args.PopValue<Vector2>()));
        }

        [DJFunc("mesh")]
        public static IDJValue BuildMesh(DJEnv env, DJSeq args)
        {
            var builder = new SubMesh<VertexData>.Builder();
            while (args.Count > 0)
            {
                var buildCmd = args.PopAs<DJList>();
                var cmdName = buildCmd.PopAs<DJSymbol>();
                var it = buildCmd.Select(x => x.Evaluate(env).GetValue<VertexData>()).GetEnumerator();
                VertexData PopVertex()
                {
                    if (it.MoveNext())
                        return it.Current;
                    return new VertexData { position = default, texCoord = default };
                }

                switch (cmdName.name)
                {
                    case "quad":
                        builder.Quad(PopVertex(), PopVertex(), PopVertex(), PopVertex());
                        break;
                    case "triangle":
                        builder.Triangle(PopVertex(), PopVertex(), PopVertex());
                        break;
                    default:
                        break;
                }
            }
            return new DJRef<SubMesh<VertexData>>(builder.Build());
        }

        [DJFunc("face")]
        public static IDJValue MakeFace(DJEnv env, DJSeq args)
        {
            var facing = (FacingMask)args.PopValue<byte>();

            var converter = args.PopManaged<Func<Vector2, Vector2>>();
            
            var mesh = args.PopManaged<SubMesh<VertexData>>();
            mesh.SetVertices(x => new VertexData(x.position, converter(x.texCoord)));

            return new DJRef<BlockModel.Face>(new BlockModel.Face(facing, mesh));
        }

        [DJMacro("model")]
        public static IDJValue MakeModel(DJEnv env, DJSeq args)
        {
            var opacity = (FacingMask)args.Pop().Evaluate(env).GetValue<byte>();

            return new DJRef<BlockModelDef>(new BlockModelDef(opacity, args.PopAs<DJList>(), args.Pop()));
        }


        [DJMacro("model-variant")]
        public static IDJValue MakeModelVariant(DJEnv env, DJSeq args)
        {
            var modelName = args.PopManaged<string>();
            if (!modelName.Contains(':'))
            {
                var ns = env.Get("namespace").GetManaged<string>();
                modelName = ns + ':' + modelName;
            }
            return new DJRef<BlockModelDefVariant>(new BlockModelDefVariant(modelName, args.PopAs<DJList>()));
        }

        [DJFunc("texture-bind")]
        public static IDJValue MakeBinds(DJEnv env, DJSeq args)
        {
            List<(DJSymbol, string, Vector4)> ans = new();
            var binds = args.PopAs<DJList>();
            while (binds.Count > 0)
            {
                var parameters = binds.PopAs<DJList>();
                var name = parameters.PopAs<DJSymbol>();
                var textureName = parameters.PopManaged<string>();
                var uv = parameters.Count > 0 ? parameters.PopValue<Vector4>() : new Vector4(0, 0, 1, 1);

                ans.Add((name, textureName, uv));
            }
            return new DJRef<List<(DJSymbol, string, Vector4)>>(ans);
        }

        [DJFunc("get-model")]
        public static IDJValue GetBlockModelDef(DJEnv env, DJSeq args)
        {
            var res = env.Get("res").GetManaged<GameResource>();
            return new DJRef<BlockModelDef>(res.modelDefs[args.PopManaged<string>()]);
        }

        [DJFunc("block")]
        public static IDJValue MakeBlock(DJEnv env, DJSeq args)
        {
            var getBinds = args.Pop();
            var getModelDef = args.Pop();
            return new DJRef<Block>(new Block(getBinds, getModelDef));
        }
    }
}
